package com.atguigu.gmall.index.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.annotation.GmallCache;
import com.atguigu.gmall.index.fegin.GmallPmsClient;
import com.atguigu.gmall.index.utils.DistributedLock;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import sun.util.resources.cldr.ru.TimeZoneNames_ru;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Service
public class IndexService {

    @Autowired
    private GmallPmsClient pmsClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String KEY_PREFIX = "index:cates:";
    private static final String LOCK_PREFIX = "index:cates:lock:";

    @Autowired
    private DistributedLock lock;

    @Autowired
    private RedissonClient redissonClient;

    public List<CategoryEntity> queryLvl1Categories() {
        ResponseVo<List<CategoryEntity>> responseVo = this.pmsClient.queryCategoriesByPid(0l);
        return responseVo.getData();
    }

    @GmallCache(prefix = KEY_PREFIX, timeout = 259200, random = 43200, lock = LOCK_PREFIX)
    public List<CategoryEntity> queryLvl2WithSubsByPid(Long pid) {
        ResponseVo<List<CategoryEntity>> responseVo = this.pmsClient.querylvl2WithSubsByPid(pid);
        List<CategoryEntity> categoryEntities = responseVo.getData();
        return categoryEntities;
    }

    public List<CategoryEntity> queryLvl2WithSubsByPid2(Long pid) {
        // 1.先查缓存，如果命中，直接返回
        String json = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
        if (StringUtils.isNotBlank(json)) {
            return JSON.parseArray(json, CategoryEntity.class);
        }

        // 为了防止缓存击穿，添加分布式锁
        RLock lock = this.redissonClient.getLock(LOCK_PREFIX + pid);
        lock.lock();

        try {
            // 在阻塞获取分布式锁的过程中，可能有其他请求已经放入缓存了，此时再次确认缓存中是否存在，存在直接返回
            String json2 = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
            if (StringUtils.isNotBlank(json2)) {
                return JSON.parseArray(json2, CategoryEntity.class);
            }

            // 2.没有命中，直接远程调用查询数据库，放入缓存
            ResponseVo<List<CategoryEntity>> responseVo = this.pmsClient.querylvl2WithSubsByPid(pid);
            List<CategoryEntity> categoryEntities = responseVo.getData();
            // 为了防止缓存穿透，数据即使为null也缓存，只是缓存时间较短，一般不超过5min
            if (CollectionUtils.isEmpty(categoryEntities)) {
                this.redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categoryEntities), 5, TimeUnit.MINUTES);
            } else {
                // 为了防止缓存雪崩，给缓存时间添加随机值
                this.redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categoryEntities), 90 + new Random().nextInt(15), TimeUnit.DAYS);
            }

            return categoryEntities;
        } finally {
            lock.unlock();
        }
    }

    public void testLock() {

        //加锁
        RLock lock = this.redissonClient.getLock("lock");
        lock.lock();

        String number = this.redisTemplate.opsForValue().get("number");
        if (StringUtils.isBlank(number)) {
            this.redisTemplate.opsForValue().set("number", "1");
        }
        int num = Integer.parseInt(number);
        this.redisTemplate.opsForValue().set("number", String.valueOf(++num));

        try {
            TimeUnit.SECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        this.testSubLock();

        lock.unlock();
    }

    public void testSubLock() {
        RLock lock = this.redissonClient.getLock("lock");
        lock.lock();
        System.out.println("测试可重入锁。。。");
        lock.unlock();
    }

    public void testLock3() {

        String uuid = UUID.randomUUID().toString();
        Boolean lock = this.lock.lock("lock", uuid, 30);

        if (lock) {
            String number = this.redisTemplate.opsForValue().get("number");
            if (StringUtils.isBlank(number)) {
                this.redisTemplate.opsForValue().set("number", "1");
            }
            int num = Integer.parseInt(number);
            this.redisTemplate.opsForValue().set("number", String.valueOf(++num));

            try {
                TimeUnit.SECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //this.testSubLock(uuid);

            this.lock.unlock("lock", uuid);
        }
    }

    public void testLock2() {
        String uuid = UUID.randomUUID().toString();
        // 获取锁
        Boolean lock = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        if (!lock) {
            // 获取锁失败，自旋
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            //this.redisTemplate.expire("lock", 3, TimeUnit.SECONDS);

            String number = this.redisTemplate.opsForValue().get("number");
            if (StringUtils.isBlank(number)) {
                this.redisTemplate.opsForValue().set("number", "1");
            }
            int num = Integer.parseInt(number);
            this.redisTemplate.opsForValue().set("number", String.valueOf(++num));

            // 释放锁
            // 为了防止误删，先判断是否自己的锁 保证判断和删除的原子性 使用 lua
            String script = "if(redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('del', KEYS[1]) else return 0 end";
            this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);
//            if (StringUtils.equals(uuid, this.redisTemplate.opsForValue().get("lock"))){
//                this.redisTemplate.delete("lock");
//            }
        }
    }

    public static void main(String[] args) {

        BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 20, 0.3);
        bloomFilter.put("1");
        bloomFilter.put("2");
        bloomFilter.put("3");
        bloomFilter.put("4");
        bloomFilter.put("5");
        bloomFilter.put("6");
        bloomFilter.put("7");
        System.out.println(bloomFilter.mightContain("1"));
        System.out.println(bloomFilter.mightContain("3"));
        System.out.println(bloomFilter.mightContain("5"));
        System.out.println(bloomFilter.mightContain("7"));
        System.out.println(bloomFilter.mightContain("8"));
        System.out.println(bloomFilter.mightContain("9"));
        System.out.println(bloomFilter.mightContain("10"));
        System.out.println(bloomFilter.mightContain("11"));
        System.out.println(bloomFilter.mightContain("12"));
        System.out.println(bloomFilter.mightContain("13"));
        System.out.println(bloomFilter.mightContain("14"));
        System.out.println(bloomFilter.mightContain("15"));
        System.out.println(bloomFilter.mightContain("16"));
        System.out.println(bloomFilter.mightContain("17"));
        System.out.println(bloomFilter.mightContain("18"));


//        System.out.println(System.currentTimeMillis());
//        new Timer().schedule(new TimerTask() {
//            @Override
//            public void run() {
//                System.out.println("这是定时器任务：" + System.currentTimeMillis());
//            }
//        }, 5000, 10000);

//        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
//        System.out.println(System.currentTimeMillis());
//        executorService.scheduleAtFixedRate(() -> {
//            System.out.println("这是定时任务：" + System.currentTimeMillis());
//        }, 5, 10, TimeUnit.SECONDS);
    }

    public void testReadLock() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("rwLock");
        rwLock.readLock().lock(10, TimeUnit.SECONDS);
        System.out.println("===================业务操作===");
        //rwLock.readLock().unlock();
    }

    public void testWriteLock() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("rwLock");
        rwLock.writeLock().lock(10, TimeUnit.SECONDS);

        System.out.println("=================================");
    }

    public void testLatch() throws InterruptedException {
        RCountDownLatch cdl = this.redissonClient.getCountDownLatch("cdl");
        cdl.trySetCount(6);
        cdl.await();

        System.out.println("=========");
    }

    public void testCountDown() {
        RCountDownLatch cdl = this.redissonClient.getCountDownLatch("cdl");
        cdl.countDown();

        System.out.println("================");
    }

}
